package nachos.vm;

import java.util.Hashtable;
import java.util.LinkedList;

import nachos.machine.*;

import nachos.userprog.*;


/**
 * A <tt>UserProcess</tt> that supports demand-paging.
 */
public class VMProcess extends UserProcess {
    /**
     * Allocate a new process.
     */

    public VMProcess() {
	super();
    }

    /**
     * Save the state of this process in preparation for a context switch.
     * This method invalidates all the TLB entries and updates all modifications
     * in the IPT.
     * Called by <tt>UThread.saveState()</tt>.
     */
    @Override
    public void saveState() {
	super.saveState();
        Processor processor = Machine.processor();
        for(int i = 0; i < processor.getTLBSize();i++){
            TranslationEntry old = processor.readTLBEntry(i);
        if(old != null && old.valid && (old.used|| old.dirty)){
             TranslationEntry oldIPT = IPTable.get(doIPTKey(this.pid, old.vpn));
             if(oldIPT != null){
                 oldIPT.dirty |= old.dirty;
                 oldIPT.used |= old.used;
             }
        }
            old = new TranslationEntry(old);
            old.valid = false;
            processor.writeTLBEntry(i, old);
        }
    }
   /**
    * Invalidates an TLB entry wich has the vpn sent.
    *
    * @param vpn
    */
    public void invalidateTLBEntry(int vpn){
        Processor processor = Machine.processor();
        for(int i = 0; i < processor.getTLBSize();i++){
            TranslationEntry e = processor.readTLBEntry(i);
            if(e.vpn == vpn){
                e.valid = false;
                processor.writeTLBEntry(i, e);
                break;
            }
        }
    }
    /**
     * This method recieves a translation entry and checks if the
     * translation entry has dirty = true if so, returns immediately.
     *
     * It calls the VMKernel functions to save the page to disk and
     * inserts a swap table entry related to the  page.
     * @param r a translation entry
     * @return True if success or false on failure to copy to disk.
     */
    public boolean moveToDisk(TranslationEntry r){
        ////System.out.println("guardo a sswap");
        SwapTableEntry e = null;
        if(!r.dirty)
            return true;
        if(VMProcess.freeSwapPages.isEmpty()){
            e = new SwapTableEntry(swapAreaSize,r);
            swapAreaSize++;
        }else{
            e = new SwapTableEntry(VMProcess.freeSwapPages.removeLast(),r);
        }
        swapTable.put(doSwapTableKey(this.pid, r.vpn), e);
        return ((VMKernel)VMKernel.kernel).savePageToSwap(swapAreaSize, pid);
    }
    /**
     *  Retrieves a page from disk and saves the data in the ppn sent.
     *  This method checks if the page is in the swap file or if we have to do
     *  lazy loading.
     * @param vpn
     * @param ppn
     * @return The translation entry associated to the obtained page.
     */
    public TranslationEntry getFromDisk(int vpn, int ppn){
        //System.out.println("busco pag en disco y doy donde subirla: " + vpn + ":" + ppn);
        TranslationEntry entry = null;
        SwapTableEntry e = VMProcess.swapTable.get(doSwapTableKey(this.pid,vpn));
        //we are talking about a coff section or stack wich isn't laoded or is readonly
        if (e == null){
            for(int i = 0; i <this.coff.getNumSections();i++){
                CoffSection cs = this.coff.getSection(i);
                int spn = 0;
                if(cs.getFirstVPN() <= vpn &&
                   (spn = vpn - cs.getFirstVPN()) < cs.getLength()){
                    cs.loadPage(spn, ppn);
                    ////System.out.println(Machine.processor().getMemory()[0]);
                    entry = new TranslationEntry(vpn, ppn, true, cs.isReadOnly(),
                            false, false);
                    break;
                }
                continue;
            }

            //ok almost sure, it is an stack page
            if(entry == null &&  vpn < this.numPages){
                ////System.out.println("ok esta pag es de stack" + vpn + ":" + ppn);
                entry = new TranslationEntry(vpn, ppn, true, false,
                            false, false);
            }
           
        } else {
           ((VMKernel)VMKernel.kernel).getPageFromSwap(e.swapOffset, ppn);
           VMProcess.swapTable.remove(doSwapTableKey(this.pid,vpn));
           freeSwapPages.add(e.swapOffset);
           entry = e.entry;
           entry.ppn = ppn;
           entry.valid = true;
           entry.used = true;
        }
        if(entry != null){
            IPTable.put(doIPTKey(this.pid,vpn), entry);
        }else{
            //System.out.println("shhhh");
        }
        return entry;
    }
    /**
     * This method generates the key value to use with the hash table representing
     * the swap file.
     * @param pid
     * @param vpn
     * @return the key made with the parameters.
     */
    public static long doSwapTableKey(int pid, int vpn){
        return doIPTKey(pid,vpn);
    }
    public static long doIPTKey(int pid, int vpn){
        return ((long)pid << Integer.SIZE) | vpn;
    }
    /**
     *  Invalidates all the TLB entries
     */
    @Override
    public void restoreState() {
        Processor processor = Machine.processor();
        for(int i = 0; i < processor.getTLBSize();i++){
            TranslationEntry old = processor.readTLBEntry(i);
            old.valid = false;
            processor.writeTLBEntry(i, old);
        }
    }
    /**
     * Uses the clock algorithm
     * @return the candidate of being replaced.
     */
    private MemoryMapEntry clockAlgorithm(){
        MemoryMapEntry e = null;
        boolean used;
        while(true){
            e = memoryMap[clockCounter];
            used = e.entry.used;
            e.entry.used = false;
            clockCounter = clockCounter < Machine.processor().getNumPhysPages() - 1?
                clockCounter + 1: 0;
            if(!used){
                //System.out.println("clock mmm");
                return e;
            }
        }
    }
    /**
     * This method is called each time we have a TLB miss, Page fault or we use
     * write or read virtual memory.
     *
     * This method checks if the
     *
     * @param vpn
     * @return the translation entry associated to the vpn
     */
    @Override
    protected TranslationEntry getVirtualPage(int vpn){
        ////System.out.println("Me pidieron una pagina: " + vpn);
        TranslationEntry e = IPTable.get(doIPTKey(this.pid,vpn));
        if(e == null){
            VMProcess.pageAccess.acquire();
            Integer page = VMKernel.getFreePage();
            VMProcess.pageAccess.release();
            // page fault
            if (page == null){
                MemoryMapEntry me = clockAlgorithm();
                me.entry.valid = false;
                this.moveToDisk(me.entry);
                IPTable.remove(doIPTKey(me.pid,me.entry.vpn));
                page = me.entry.ppn;
                if(me.pid == this.pid)
                    invalidateTLBEntry(me.entry.vpn);
            }
             e = this.getFromDisk(vpn, page);
             //e.used = true;
             memoryMap[page] = new MemoryMapEntry(this.pid,e);
            
        }
        Processor processor = Machine.processor();
        int r = Lib.random(processor.getTLBSize());
        TranslationEntry old = processor.readTLBEntry(r);
        if(old.valid && (old.used|| old.dirty)){
             TranslationEntry oldIPT = IPTable.get(doIPTKey(this.pid, old.vpn));
             if(oldIPT!= null){
                 oldIPT.dirty |= old.dirty;
                 oldIPT.used |= old.used;
             }
             
        }

        processor.writeTLBEntry(r,e);
        ////System.out.println("Retornando translation entry r: " +r + " "+ e.vpn + ":" + e.ppn + e.valid);
        return e;
    }
    /**
     * Initializes page tables for this process so that the executable can be
     * demand-paged.
     *
     * @return	<tt>true</tt> if successful.
     */
    @Override
    protected boolean loadSections() {
        ////System.out.println("id: " + this.pid + "num pages:" + this.numPages);
	return true;
    }

    /**
     * Release any resources allocated by <tt>loadSections()</tt>.
     */
    @Override
    protected void unloadSections() {
        this.pageAccess.acquire();
	for(int i = 0; i < this.numPages; i++){
            TranslationEntry e = VMProcess.IPTable.get(doIPTKey(this.pid,i));
            if(e != null){
                e.valid = false;
                VMKernel.returnFreePage(e.ppn);
                VMProcess.IPTable.remove(doIPTKey(this.pid,i));
            }
        }
        this.pageAccess.release();
        for(int i = 0; i < this.numPages; i++){
            SwapTableEntry e = VMProcess.swapTable.get(doSwapTableKey(this.pid, i));
            if(e != null){
                VMProcess.freeSwapPages.addLast(e.swapOffset);
                VMProcess.swapTable.remove(doSwapTableKey(this.pid, i));
                e.entry.valid = false;
            }
        }
    }    

    /**
     * Handle a user exception. Called by
     * <tt>UserKernel.exceptionHandler()</tt>. The
     * <i>cause</i> argument identifies which exception occurred; see the
     * <tt>Processor.exceptionZZZ</tt> constants.
     *
     * @param	cause	the user exception that occurred.
     */
    @Override
    public void handleException(int cause) {
	Processor processor = Machine.processor();

	switch (cause) {
        case Processor.exceptionTLBMiss:
        case Processor.exceptionPageFault:
            ////System.out.println("TLB Miss => "+ Processor.pageFromAddress(processor.readRegister( Processor.regBadVAddr)));
            this.getVirtualPage(Processor.pageFromAddress(processor.readRegister( Processor.regBadVAddr)));
            //processor.advancePC();
            break;

	default:
            if(cause > 0)
                    ////System.out.println(cause);
            ((VMKernel)VMKernel.kernel).swapFile.close();
	    super.handleException(cause);

	    break;
	}
    }
    class SwapTableEntry{
        TranslationEntry entry;
        int swapOffset;
        SwapTableEntry(int off, TranslationEntry e){
            this.swapOffset = off;
            this.entry = e;
        }
    }
    class MemoryMapEntry{
        TranslationEntry entry;
        int pid;
        MemoryMapEntry(int id, TranslationEntry e){
            this.pid = id;
            this.entry = e;
        }
    }
    private static Hashtable<Long, SwapTableEntry> swapTable = new Hashtable() ;
    private static LinkedList<Integer> freeSwapPages = new LinkedList<Integer>();
    private static int clockCounter = 0;
    private static int swapAreaSize = 0;
    private static MemoryMapEntry[] memoryMap = new MemoryMapEntry[Machine.processor().getNumPhysPages()];
    private static Hashtable<Long, TranslationEntry> IPTable = new Hashtable() ;
    private static final int pageSize = Processor.pageSize;
    private static final char dbgProcess = 'a';
    private static final char dbgVM = 'v';
}
